#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <string.h>
#include <malloc.h>

#include "cfd-elf.h"
#include "cfd-proc.h"
#include "cfd-filter.h"
#include "cfd-mmap.h"
#include "cfd-ptrace.h"

/* Resolve symbol and call it */

struct syms_filter_context
{
    const char * symbol_name;
    const char * symbol_lib;
    
    char * __data;
};

int syms_filter (pid_t pid
                 , struct  user_regs_struct * registers
                 , void ** text             __attribute__((unused)) 
                 , long *  text_size        __attribute__((unused)) 
                 , long *  text_addr        __attribute__((unused)) 
                 , void ** data             __attribute__((unused)) 
                 , long *  data_size        __attribute__((unused)) 
                 , void *  filter_context  
                 )
{
    int retval = 1;
    
    struct syms_filter_context * ctx = filter_context;

    if (NULL == ctx)
    {
        fprintf(stderr
                , "Filter context is NULL. Disable filter\n"
                );
        
        return retval;
    }
    
    struct map * maps = parse_proc_maps (pid, MODE_RX);
    struct map * lib  = find_first_map(maps, ctx->symbol_lib);

    if (NULL == lib)
    {
        fprintf(stderr
                , "Couldn't find library %s in %d maps\n"
                , ctx->symbol_lib
                , pid
                );
        
        goto lbEnd;
    }

    struct mfile * mfile = mmap_file(lib->name
                                     , 0
                                     , O_RDONLY
                                     );

    if (NULL == mfile)
        goto lbEnd;

    void * symbol = search_symbol(mfile->memory
                                  , ctx->symbol_name
                                  );

    if (NULL == symbol)
    {
        fprintf(stderr
                , "Couldn't find symbol %s in %s\n"
                , ctx->symbol_name
                , ctx->symbol_lib
                );
        
        goto lbEnd2;
    }
    
    /* Ok. We found relative offset and map vptr.
     * Add one to another and put to R_1 register. */

    registers->R_1 = (long) (symbol - mfile->memory)
                     + (long) lib->address;

    /* Zeroing R_0 retval register */
    registers->R_0 = 0;
    
    fprintf(stderr
            , "Setup R_1 to %p [%p + %p]\n"
            , (void *) registers->R_1
            , (void *) (symbol - mfile->memory)
            , lib->address
            );
    
    retval = 0;
    
    /* Filter installation done */
    
  lbEnd2:
    munmap_file(mfile);

    
  lbEnd:
    free_maps(maps);

    return retval;
}

void * syms_constructor (const char * arg)
{
    /* Search -L argument
     * If not, return NULL */

    struct syms_filter_context * ctx =
        (struct syms_filter_context *) malloc(sizeof(*ctx));
    
    if ((NULL == ctx)
        || (NULL == arg))
        return NULL;
    
    fprintf(stderr
            , "syms: arg: %s\n"
            , arg
            );

    ctx->__data = strdup(arg);
    ctx->symbol_lib  = ctx->__data;
    ctx->symbol_name = strchr(ctx->__data, ':');
    
    * ((char *) ctx->symbol_name ++) = 0;
        
    if (NULL == ctx->symbol_name)
    {
        free(ctx->__data);
        free(ctx);
        return NULL;
    }
    
    return ctx;
}

void syms_destructor(void * context)
{
    struct syms_filter_context * ctx = context;
    
    free(ctx->__data);
    free(ctx);
}

/* ====================================== */

static struct provide __syms_provide = 
{
    .filter      = syms_filter,
    .constructor = syms_constructor,
    .destructor  = syms_destructor,
};

const struct provide * syms_init(void)
{
    return & __syms_provide;
}
